package application;

import ASAInstruction.ASAInstruction;
import exceptions.ExecutionException;
import exceptions.AnalyseException;
import exceptions.NoMainFoundError;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import jjclass.PicolInterpreter;
import jjclass.ParseException;

/**
 * Classe representant le programme
 * @author O.Riad
 * @author R.Dubos
 */
public class Program implements InterfaceApplication {

    private int mode;
    private Stack<ASAInstruction> debugger;
    private PicolInterpreter picolInterpreter;
    private boolean isDebug;
    private boolean isNormal;

    /**
     * Constructeur du Programme
     * par defaut (mode normal)
     */
    public Program() {
        this.mode = Program.MODE_NORMAL;
        this.debugger = new Stack<ASAInstruction>();
    }

    /**
     * Constructeur prenant en paramètre le mode
     * @param MODE mode debug ou normal
     */
    public Program(int MODE) {
        if (MODE == Program.MODE_DEBUG || MODE == Program.MODE_NORMAL) {
            this.mode = MODE;
        }
        this.debugger = new Stack<ASAInstruction>();
        this.isDebug = false;
    }
    
    /**
     * Ajoute un fichier contenant le
     * code à exécuter au programme 
     * @param input 
     */
    @Override
    public void addProgram(InputStream input) {
        this.picolInterpreter = new PicolInterpreter(input);
    }

    /**
     * Methode se chargeant d'executer le code source de l'application
     *
     * @return int Le résultat de l'exécution
     */
    @Override
    public int execute() throws NoMainFoundError,ExecutionException {
        if (FunctionStack.exists("main")) {
            if (this.mode == Program.MODE_NORMAL) {
                FunctionStack.setExecCurrentFunction("main");
                FunctionStack.getCode("main").execute();
            }
        } else {
            throw new NoMainFoundError();            
        }
        System.out.println("\n----END-----");
        System.err.println("Memoire : " + Memory.getMap());
        System.err.println("FunctionStack :" + FunctionStack.getMap());
        return 1;
    }

    /**
     * Méthode permettant de 
     * fermer l'application
     */
    @Override
    public void close() {
        System.exit(0);
    }

    /**
     * Lance l'analyse grammaticale en mode debug
     * @param input Fichier contenant le code source
     * @throws AnalyseException 
     */
    @Override
    public void initDebug(InputStream input) throws AnalyseException{
        if (this.isDebug == false) {
            if (this.debugger.isEmpty()) {
                this.debugger = new Stack<ASAInstruction>();
                this.initProgram(input);
                List<ASAInstruction> mainBloc = FunctionStack.getCode("main").getCode();
                for (int i = mainBloc.size() - 1; i >= 0; i--) {
                    this.debugger.push(mainBloc.get(i));
                }
                FunctionStack.setExecCurrentFunction("main");
            }
        }
    }

    @Override
    public void debugExecNextLine() throws ExecutionException {
        if (this.debugger.isEmpty()) {
            this.isDebug = false;
            System.out.println("\n----END-----");
        } else {
            this.isDebug = true;
            this.debugger.pop().execute();
        }
    }

    /**
     * Passer en mode dit "debug"
     */
    @Override
    public void setDebug() {
        this.isDebug = true;
    }

    /**
     * Passer en mode dit "normal"
     */
    @Override
    public void setNormal() {
        this.isNormal = true;
        this.debugger.clear();
    }

    /**
     * Lance l'analyse grammaticale en mode normal
     * @param input Fichier contenant le code source
     * @throws AnalyseException 
     */
    @Override
    public void initProgram(InputStream input) throws AnalyseException {
        this.mode = Program.MODE_NORMAL;
        FunctionStack.destroy();
        Memory.destroy();
        this.picolInterpreter = new PicolInterpreter(input);
        try {
            this.picolInterpreter.parse();
        } catch (ParseException ex) {            
            throw new AnalyseException(ex);
        }
    }
    
    /**
     * Renvoie la Map representant les variables et leur contenu
     * @return Map des variables et de leur contenu
     */
    @Override
    public Map<String, Integer> getVariable(){
        return Memory.getFunctionMemory("main");
    }
}
